package pl.edu.agh.student.janikhankus.earley;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeSet;

import javax.swing.JTable;

import org.antlr.runtime.ANTLRFileStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;

import pl.edu.agh.student.janikhankus.exceptions.BadProbabilitySumException;
import pl.edu.agh.student.janikhankus.exceptions.DuplicatedProductionsException;
import pl.edu.agh.student.janikhankus.grammar.GrammarChecker;
import pl.edu.agh.student.janikhankus.grammar.GrammarLexer;
import pl.edu.agh.student.janikhankus.grammar.GrammarParser;
import pl.edu.agh.student.janikhankus.tree.Grammar;
import pl.edu.agh.student.janikhankus.tree.GrammarTreeCreator;
import pl.edu.agh.student.janikhankus.tree.Production;
import pl.edu.agh.student.janikhankus.tree.Symbol;

/**
 * Main class which parses given grammar and tokens (using
 * {@pl.edu.agh.student.janikhankus.earley.Earley}) and after this creating
 * parse trees
 * 
 * @author tom
 * 
 */
public class TreeMaker {
	/**
	 * @deprecated Used only for tests
	 */
	@Deprecated
	static ArrayList<MyTree> makeTreesFromFile(LinkedList<String> list,
			String fileName) {
		GrammarLexer lex;

		try {
			lex = new GrammarLexer(new ANTLRFileStream(fileName));
			CommonTokenStream tokens = new CommonTokenStream(lex);
			GrammarParser parser = new GrammarParser(tokens);
			parser.productions();
			GrammarChecker grammarChecker = new GrammarChecker();
			GrammarTreeCreator grammarTreeCreator = new GrammarTreeCreator();
			Grammar grammar = grammarTreeCreator.createGrammarTree(
					parser.getTokenNames(), tokens);
			grammarChecker.checkSemantic(grammar);
			grammarChecker.addTemporaryProductions(grammar);

			return makeTrees(list, grammar);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (RecognitionException e) {
			e.printStackTrace();
		} catch (BadProbabilitySumException e) {
			e.printStackTrace();
		} catch (DuplicatedProductionsException e) {
			e.printStackTrace();
		}
		return null;

	}

	/**
	 * Parse given grammar and create parse trees.
	 * 
	 * @param list
	 *            Tokens list (parse word)
	 * @param grammar
	 *            Parse grammar
	 * @return List of possible parse trees
	 */
	public static ArrayList<MyTree> makeTrees(LinkedList<String> list,
			Grammar grammar) {
		LinkedList<Production> productionList = grammar.getProductionList();
		productionList.remove(0);
		grammar.setProductionList(productionList);

		ArrayList<MyTree> returnTrees = null;
		Earley earley = new Earley();

		ArrayList<OrderedDict> parseSets = earley.parse(grammar, list,
				earley.findNonTerminals(grammar));

		ArrayList<Symbol> headList = new ArrayList<Symbol>();
		headList.add((grammar.getProductionList().get(0).getLeft()));
		// precedens list for null -> S | return's "nodes"
		ParseProduction root = new ParseProduction(0, 1, list.size(), null,
				headList, 0);
		TreeSet<Precedents> nodes = parseSets.get(list.size()).d.get(root);

		returnTrees = new ArrayList<MyTree>();
		Forest forest = new Forest();
		if (nodes != null) {
			for (Precedents nodeIter : nodes) {
				ArrayList<ArrayList<MyTree>> forests = forest.forest(nodeIter,
						parseSets, null);
				for (ArrayList<MyTree> a : forests) {
					for (MyTree b : a) {
						returnTrees.add(b);
					}
				}
			}
		}
		for (MyTree tree : returnTrees) {
			delTmpTerminals(tree, grammar.getTemporaryProductions());
		}

		return deleteDuplicatedTrees(returnTrees, 0);
		//return returnTrees;

	}

	private static ArrayList<MyTree> deleteDuplicatedTrees(
			ArrayList<MyTree> treeList, int lvl) {
		ArrayList<MyTree> returnList = new ArrayList<MyTree>();
		if (treeList.size() == 1) {
			return treeList;
		}
		//Two trees have this same category if theirs every node from top to specified level are this same
		HashMap<String, ArrayList<MyTree>> categories = new HashMap<String, ArrayList<MyTree>>();
		//Sorting each tree for categories
		for (MyTree tree : treeList) {
			String lvlName = getTreeSpecifiedLevelChildrenNames(tree, 0, lvl);
			if (categories.containsKey(lvlName)) {
				categories.get(lvlName).add(tree);
			} else {
				ArrayList<MyTree> tmpTreeList = new ArrayList<MyTree>();
				tmpTreeList.add(tree);
				categories.put(lvlName, tmpTreeList);
			}
		}
		for (String name : categories.keySet()) {
			ArrayList<MyTree> tmpTreeList = categories.get(name);
			if (tmpTreeList.size() == 1) {
				returnList.add(tmpTreeList.get(0));
			} else {
				int height = treeHeight(tmpTreeList.get(0),0);
				//It's last tree level, so tree are this same
				if(height == lvl){
					returnList.add(tmpTreeList.get(0));
				}
				//Passing category to next lvl
				else{
					returnList.addAll(deleteDuplicatedTrees(categories.get(name), lvl+1));
				}
			}
		}

		return returnList;
	}

	// private
	static String getTreeSpecifiedLevelChildrenNames(MyTree tree,
			int actualLvl, int searchLvl) {
		String name = "";
		ArrayList<MyTree> childer = tree.getChilder();
		if (childer != null) {
			if (actualLvl == searchLvl) {
				for (MyTree tmpTree : childer) {
					name = name + tmpTree.toString();
				}
				return name;
			}
			for (MyTree tmpTree : childer) {
				name = name
						+ getTreeSpecifiedLevelChildrenNames(tmpTree,
								actualLvl + 1, searchLvl);
			}
			return name;
		} else {
			return "";
		}
	}

	static int treeHeight(MyTree tree, int lvl) {
		ArrayList<MyTree> list = tree.getChilder();
		int max = 0;
		if (list != null) {
			for (MyTree tmpTree : list) {
				int tmpTreeSiz = treeHeight(tmpTree, lvl+1);
				if (max < tmpTreeSiz)
					max = tmpTreeSiz;
			}
		} else {
			return lvl;
		}
		return max;
	}

	static private void delTmpTerminals(MyTree tree,
			Map<String, String> tmpTerminals) {
		if (tmpTerminals.containsKey(tree.toString())) {
			tree.reset(tmpTerminals.get(tree.toString()));
		} else {
			ArrayList<MyTree> list = tree.getChilder();
			if (list != null) {
				for (MyTree tmpTree : list) {
					delTmpTerminals(tmpTree, tmpTerminals);
				}
			}
		}
	}

	// int[0] - tree height, int[1] - tree leafs widths
	static private int[] countTree(int[] startTab, MyTree tree) {
		if (startTab == null) {
			startTab = new int[2];
			startTab[0] = 1;
			startTab[1] = 1;
		}
		int[] tab;
		if (tree.childer == null) {
			tab = new int[2];
			tab[0] = 1;
			tab[1] = 1;
			return tab;
		} else {
			int[] myTab = new int[2];
			int[] tmpTab = new int[2];
			tmpTab[0] = startTab[0] + 1;
			myTab[0] = startTab[0] + 1;
			myTab[1] = 0;
			for (MyTree t : tree.childer) {
				tab = countTree(tmpTab, t);
				myTab[0] = Math.max(tab[0], myTab[0]);
				myTab[1] = myTab[1] + tab[1];
			}
			return myTab;
		}
	}

	// rows[ROWS] [COLUMNS]
	static String[][] table;
	static int columns = 0;
	static int rows = 0;
	static int max = 0;

	// Protected for Test method
	static String[][] makeTable(MyTree tree) {
		columns = 0;
		rows = 0;
		max = 0;
		int siz[] = countTree(null, tree);
		rows = siz[0];
		columns = siz[1];
		table = new String[rows][columns];

		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < columns; j++) {
				table[i][j] = "";
			}
		}
		populateTable(tree, 0, true);
		return table;
	}

	static private void populateTable(MyTree tree, int lvl, boolean isFirst) {
		if (tree != null) {
			table[lvl][findMaxColumn(isFirst)] = tree.toString();
			ArrayList<MyTree> kidsList = tree.getChilder();
			if (kidsList != null) {
				boolean firstKid = true;
				for (MyTree kid : kidsList) {
					populateTable(kid, lvl + 1, firstKid);
					firstKid = false;
				}
			}
		}
	}

	static private int findMaxColumn(boolean isFirst) {
		if (!isFirst) {
			max++;
		}
		return max;

	}

	/**
	 * Compute absolute probability of given a tree
	 * 
	 * @param tree
	 *            Some parse tree
	 * @return Absolute probability of given a tree
	 */
	static public double computeProb(MyTree tree) {
		ArrayList<MyTree> children = tree.getChilder();
		if (children == null) {
			return 1;
		} else {
			double returnVal = tree.prob;
			for (MyTree t : children) {
				returnVal = returnVal * computeProb(t);
			}
			returnVal = Math.round(returnVal * 1000000000);
			returnVal /= 1000000000;
			return returnVal;
		}
	}

	/**
	 * Creates JTable which represents parse tree
	 * 
	 * @param tree
	 *            Some parse tree
	 * @return JTable parse tree
	 */
	static public JTable getJTable(MyTree tree) {
		String[][] table = TreeMaker.makeTable(tree);

		final Object headers[] = new Object[table[0].length];
		for (int i = 0; i < table[0].length; i++) {
			headers[i] = i;
		}

		JTable jTable = new JTable((Object[][]) table, headers);
		jTable.setFillsViewportHeight(true);
		jTable.setEnabled(false);
		return jTable;

	}
}
